package jdos.cpu;

import jdos.Dosbox;
import jdos.cpu.CPU_Regs.Reg;
import jdos.debug.Debug;
import jdos.gui.Main;
import jdos.hardware.Memory;
import jdos.misc.Log;
import jdos.misc.setup.*;
import jdos.types.LogSeverities;
import jdos.types.LogTypes;
import jdos.util.IntRef;

import java.util.Hashtable;

public class CPU {

	
    public static final int CPU_AUTODETERMINE_NONE=0x00;
    public static final int CPU_AUTODETERMINE_CORE=0x01;
    public static final int CPU_AUTODETERMINE_CYCLES=0x02;

    public static final int CPU_AUTODETERMINE_SHIFT=0x02;
    public static final int CPU_AUTODETERMINE_MASK=0x03;

    public static final int CPU_CYCLES_LOWER_LIMIT=100;

    public static interface CPU_Decoder {
        public /*Bits*/int call();
    }

    public static final int CPU_INT_SOFTWARE=0x1;
    public static final int CPU_INT_EXCEPTION=0x2;
    public static final int CPU_INT_HAS_ERROR=0x4;
    public static final int CPU_INT_NOIOPLCHECK=0x8;

    public static void CPU_HW_Interrupt(/*Bitu*/int num) {
        CPU_Interrupt(num,0,CPU_Regs.reg_eip);
    }
    public static void CPU_SW_Interrupt(/*Bitu*/int num,/*Bitu*/int oldeip) {
        CPU_Interrupt(num,CPU_INT_SOFTWARE,oldeip);
    }
    public static void CPU_SW_Interrupt_NoIOPLCheck(/*Bitu*/int num,/*Bitu*/int oldeip) {
        CPU_Interrupt(num,CPU_INT_SOFTWARE|CPU_INT_NOIOPLCHECK,oldeip);
    }

    static public int returnValue = 0;
    
    final static public class CPUBlock {
        public /*Bitu*/int cpl;							/* Current Privilege */
        public /*Bitu*/int mpl;
        public /*Bitu*/int cr0;
        public /*Bitu*/int cr4;
        //public boolean pmode;							/* Is Protected mode enabled */
        final public class Stack {
            public /*Bitu*/int mask, notmask;
            public boolean big;
        }
        final public Stack stack = new Stack();

        final public class Hlt {
            public /*Bitu*/int cs;
            public int eip;
            public CPU_Decoder old_decoder;
        }
        final public Hlt hlt = new Hlt();
        final public class Exception {
            public /*Bitu*/int which, error;
        }
        final public Exception exception = new Exception();
        public boolean trap_skip;
    }

    public static CPU_Regs regs = new CPU_Regs();
    public static CPUBlock cpu;

    public static /*Bit32s*/int CPU_Cycles = 0;
    public static /*Bit32s*/int CPU_CycleLeft = 3000;
    public static /*Bit32s*/int CPU_CycleMax = 3000;
    public static /*Bit32s*/int CPU_OldCycleMax = 3000;
    public static /*Bit32s*/int CPU_CyclePercUsed = 100;
    public static /*Bit32s*/int CPU_CycleLimit = -1;
    public static /*Bit32s*/int CPU_CycleUp = 0;
    public static /*Bit32s*/int CPU_CycleDown = 0;
    public static /*Bit32s*/int CPU_IODelayRemoved = 0;
    public static final boolean CPU_CycleAutoAdjust = false;
    public static final boolean CPU_SkipCycleAutoAdjust = false;
    public static /*Bitu*/int CPU_AutoDetermineMode = 0;

	
    public static /*Bitu*/int CPU_extflags_toggle=0;	// ID and AC flags may be toggled depending on emulated CPU architecture

    public static /*Bitu*/int CPU_PrefetchQueueSize=0;

    /* In debug mode exceptions are tested and dosbox exits when
     * a unhandled exception state is detected.
     * USE CHECK_EXCEPT to raise an exception in that case to see if that exception
     * solves the problem.
     *
     * In non-debug mode dosbox doesn't do detection (and hence doesn't crash at
     * that point). (game might crash later due to the unhandled exception) */
    
//    private static final boolean CPU_CHECK_IGNORE = Config.C_DEBUG;

    public static void CPU_Push16(/*Bitu*/int value) {
        /*Bit32u*/int new_esp=(CPU_Regs.reg_esp.dword & cpu.stack.notmask) | ((CPU_Regs.reg_esp.dword - 2) & cpu.stack.mask);
        Memory.mem_writew(CPU_Regs.reg_ssPhys + (new_esp & cpu.stack.mask) ,value);
        CPU_Regs.reg_esp.dword=new_esp;
    }

    public static int CPU_Push16(int esp, /*Bitu*/int value) {
        /*Bit32u*/int new_esp=(esp & cpu.stack.notmask) | ((esp - 2) & cpu.stack.mask);
        Memory.mem_writew(CPU_Regs.reg_ssPhys + (new_esp & cpu.stack.mask) ,value);
        return new_esp;
    }

    public static void CPU_Push32(/*Bitu*/int value) {
        /*Bit32u*/int new_esp=(CPU_Regs.reg_esp.dword & cpu.stack.notmask)|((CPU_Regs.reg_esp.dword - 4) & cpu.stack.mask);
        Memory.mem_writed(CPU_Regs.reg_ssPhys + (new_esp & cpu.stack.mask) ,value);
        CPU_Regs.reg_esp.dword=new_esp;
    }

    public static int CPU_Push32(int esp, /*Bitu*/int value) {
        /*Bit32u*/int new_esp=(esp & cpu.stack.notmask)|((esp - 4) & cpu.stack.mask);
        Memory.mem_writed(CPU_Regs.reg_ssPhys + (new_esp & cpu.stack.mask) ,value);
        return new_esp;
    }

    public static /*Bitu*/int CPU_Pop16() {
        /*Bitu*/int val=Memory.mem_readw(CPU_Regs.reg_ssPhys + (CPU_Regs.reg_esp.dword & cpu.stack.mask));
        CPU_Regs.reg_esp.dword=(CPU_Regs.reg_esp.dword & cpu.stack.notmask)|((CPU_Regs.reg_esp.dword + 2 )& cpu.stack.mask);
        return val;
    }

    public static /*Bitu*/int CPU_Peek16(int index) {
        return Memory.mem_readw(CPU_Regs.reg_ssPhys + (CPU_Regs.reg_esp.dword+index*2 & cpu.stack.mask));
    }

    public static int CPU_Peek32(int index) {
        return Memory.mem_readd(CPU_Regs.reg_ssPhys + (CPU_Regs.reg_esp.dword+index*4 & cpu.stack.mask));
    }

    public static /*Bitu*/int CPU_Pop32() {
        /*Bitu*/int val=Memory.mem_readd(CPU_Regs.reg_ssPhys + (CPU_Regs.reg_esp.dword & cpu.stack.mask));
        CPU_Regs.reg_esp.dword=(CPU_Regs.reg_esp.dword & cpu.stack.notmask)|((CPU_Regs.reg_esp.dword + 4)&cpu.stack.mask);
        return val;
    }

	static public boolean CPU_POPF(/*Bitu*/boolean use32) {
		/* IOPL field can only be modified when CPL=0 or in real mode: */
		if (use32) {
			CPU_Regs.CPU_SetFlags(CPU_Pop32(), CPU_Regs.FMASK_ALL);
		} else {
			CPU_Regs.CPU_SetFlags(CPU_Pop16(), CPU_Regs.FMASK_ALL & 0xffff);
		}
		return false;
	}

    static public boolean CPU_PUSHF(/*Bitu*/boolean use32) {
        Flags.FillFlags();
        if (use32)
            CPU_Push32(CPU_Regs.flags & 0xfcffff);
        else CPU_Push16(CPU_Regs.flags);
        return false;
    }


    public static void CPU_Exception( /*Bitu*/int which) {
        CPU_Exception(which, 0);
    }
    public static void CPU_Exception( /*Bitu*/int which, /*Bitu*/int error ) {
//	LOG_MSG("Exception %d error %x",which,error);
        cpu.exception.error=error;
        int flags = CPU_INT_EXCEPTION;
        if (which == 0x8 || which >= 0xA && which <= 0xE) {
            flags |= CPU_INT_HAS_ERROR;
        }
        CPU_Interrupt(which,flags,CPU_Regs.reg_eip);
    }

    static public/*Bit8u*/int lastint;

    static private void CPU_Interrupt(/*Bitu*/int num, /*Bitu*/int type, /*Bitu*/int oldeip) {
        lastint=num;
        Flags.FillFlags();
        if (Config.C_DEBUG) {
            switch (num) {
            case 0xcd:
                if (Config.C_HEAVY_DEBUG) {
                    Log.log(LogTypes.LOG_CPU,LogSeverities.LOG_ERROR, "Call to interrupt 0xCD this is BAD");
                    Debug.DEBUG_HeavyWriteLogInstruction();
                    Log.exit("Call to interrupt 0xCD this is BAD");
                }
                break;
            case 0x03:
                if (Debug.DEBUG_Breakpoint()) {
                    CPU_Cycles=0;
                    return;
                }
            }
        }

            /* Save everything on a 16-bit stack */
            int esp = CPU_Push16(CPU_Regs.reg_esp.dword, CPU_Regs.flags & 0xffff);
            esp = CPU_Push16(esp, CPU_Regs.reg_csVal.dword);
            esp = CPU_Push16(esp, oldeip & 0xFFFF);

			/* Get the new CS:IP from vector table */
			/*PhysPt*/int base = 0;//cpu.idt.GetBase();
			int eip = Memory.mem_readw(base + (num << 2));

            // do writes now since PF can not happen after this read
            CPU_Regs.SegSet16CS(Memory.mem_readw(base+(num << 2)+2));

            CPU_Regs.SETFLAGBIT(CPU_Regs.IF,false);
            CPU_Regs.SETFLAGBIT(CPU_Regs.TF,false);
            CPU_Regs.reg_esp.dword = esp;
            CPU_Regs.reg_eip = eip;
            return;
        
    }

    static public boolean iret = false;

	static public void CPU_IRET(boolean use32, /*Bitu*/int oldeip) {
		iret = true;
		if (use32) {
			CPU_Regs.reg_eip = CPU_Pop32();
			CPU_Regs.SegSet16CS(CPU_Pop32());
			CPU_Regs.CPU_SetFlags(CPU_Pop32(), CPU_Regs.FMASK_ALL);
		} else {
			CPU_Regs.reg_eip = CPU_Pop16();
			CPU_Regs.SegSet16CS(CPU_Pop16());
			CPU_Regs.CPU_SetFlags(CPU_Pop16(), CPU_Regs.FMASK_ALL & 0xffff);
		}
	}

	public static void CPU_JMP(boolean use32, /*Bitu*/int selector, /*Bitu*/int offset, /*Bitu*/int oldeip) {
		if (!use32) {
			CPU_Regs.reg_eip = offset & 0xffff;
		} else {
			CPU_Regs.reg_eip = offset;
		}
		CPU_Regs.SegSet16CS(selector);
	}

	public static void CPU_CALL(boolean use32, /*Bitu*/int selector, /*Bitu*/int offset, /*Bitu*/int oldeip) {
		int esp = CPU_Regs.reg_esp.dword;
		if (!use32) {
			esp = CPU_Push16(esp, CPU_Regs.reg_csVal.dword);
			esp = CPU_Push16(esp, oldeip & 0xFFFF);
			CPU_Regs.reg_eip = offset & 0xffff;
		} else {
			esp = CPU_Push32(esp, CPU_Regs.reg_csVal.dword);
			esp = CPU_Push32(esp, oldeip);
			CPU_Regs.reg_eip = offset;
		}
		CPU_Regs.reg_esp.dword = esp; // don't set ESP until we are done with Memory Writes / CPU_Push so that we are reentrant
		CPU_Regs.SegSet16CS(selector);
	}

	static public void CPU_RET(boolean use32, /*Bitu*/int bytes, /*Bitu*/int oldeip) {

		/*Bitu*/int new_ip;
		int new_cs;
		if (!use32) {
			new_ip = CPU_Pop16();
			new_cs = CPU_Pop16();
		} else {
			new_ip = CPU_Pop32();
			new_cs = CPU_Pop32() & 0xffff;
		}
		CPU_Regs.reg_esp.dword += bytes;
		CPU_Regs.SegSet16CS(new_cs);
		CPU_Regs.reg_eip = new_ip;

	}

    static boolean printed_cycles_auto_info = false;

    static public boolean CPU_SetSegGeneral(int seg, /*Bitu*/int value) {
        value &= 0xffff;
            CPU_Regs.SegSet(seg, value,value << 4);
            return false;
        
    }

    static public boolean CPU_SetSegGeneralES(/*Bitu*/int value) {
        value &= 0xffff;
            CPU_Regs.SegSet16ES(value);
            return false;
        
    }

    static public boolean CPU_SetSegGeneralCS(/*Bitu*/int value) {
        value &= 0xffff;
            CPU_Regs.SegSet16CS(value);
            return false;
        
    }

    static public boolean CPU_SetSegGeneralDS(/*Bitu*/int value) {
        value &= 0xffff;
            CPU_Regs.SegSet16DS(value);
            return false;
        
    }

    static public boolean CPU_SetSegGeneralFS(/*Bitu*/int value) {
        value &= 0xffff;
            CPU_Regs.SegSet16FS(value);
            return false;
        
    }

    static public boolean CPU_SetSegGeneralGS(/*Bitu*/int value) {
        value &= 0xffff;
            CPU_Regs.SegSet16GS(value);
            return false;
        
    }

    static public boolean CPU_SetSegGeneralSS(/*Bitu*/int value) {
        value &= 0xffff;
            CPU_Regs.SegSet16SS(value);
            cpu.stack.big=false;
            cpu.stack.mask=0xffff;
            cpu.stack.notmask=0xffff0000;
            return false;
        
    }

    static public boolean CPU_SetSegGeneral_indexNew(int seg,/*Bitu*/int value) {
        switch (seg) {
            case CPU_Regs.es: return CPU_SetSegGeneral(CPU_Regs.es, value);
            case CPU_Regs.cs: return CPU_SetSegGeneral(CPU_Regs.cs, value);
            case CPU_Regs.ss: {if(CPU_SetSegGeneralSS(value)) {return true;}else{Core.base_ss=CPU_Regs.reg_ssPhys;return false;}}
            case CPU_Regs.ds: {if(CPU_SetSegGeneral(CPU_Regs.ds, value)) {return true;}else{Core.base_ds=CPU_Regs.reg_dsPhys;return false;}}
            case CPU_Regs.fs: return CPU_SetSegGeneral(CPU_Regs.fs, value);
            case CPU_Regs.gs: return CPU_SetSegGeneral(CPU_Regs.gs, value);
            default:
                Log.exit("Unknown segment");
                return false;
        }
    }

	static public int CPU_getSegGeneral_index(int seg) {
		switch (seg) {
		case CPU_Regs.es:			return CPU_Regs.reg_esVal.word();
		case CPU_Regs.cs:			return CPU_Regs.reg_csVal.word();
		case CPU_Regs.ss:			return CPU_Regs.reg_ssVal.word();
		case CPU_Regs.ds:			return CPU_Regs.reg_dsVal.word();
		case CPU_Regs.fs:			return CPU_Regs.reg_fsVal.word();
		case CPU_Regs.gs:			return CPU_Regs.reg_gsVal.word();
		default:
			return 0;
		}
	}

	static public String getSEG(int seg) {

		String name = "";
		switch (seg) {
		case CPU_Regs.es:		name = "es";	 break;
		case CPU_Regs.cs:		name = "cs";	 break;
		case CPU_Regs.ss:		name = "ss";	 break;
		case CPU_Regs.ds:		name = "ds";	 break;
		case CPU_Regs.fs:		name = "fs";	 break;
		case CPU_Regs.gs:		name = "gs";	 break;
		}
        return name;
	}

	static public String DO_PREFIX_SEG_index(int seg) {
		int reg = 0;
		String name = "";
		switch (seg) {
		case CPU_Regs.es:		name = "es";	reg = CPU_Regs.reg_esPhys; break;
		case CPU_Regs.cs:		name = "cs";	reg = CPU_Regs.reg_csPhys; break;
		case CPU_Regs.ss:		name = "ss";	reg = CPU_Regs.reg_ssPhys; break;
		case CPU_Regs.ds:		name = "ds";	reg = CPU_Regs.reg_dsPhys; break;
		case CPU_Regs.fs:		name = "fs";	reg = CPU_Regs.reg_fsPhys; break;
		case CPU_Regs.gs:		name = "gs";	reg = CPU_Regs.reg_gsPhys; break;
		}
        Core.base_ds=reg;
        Core.base_ss=reg;
        return name;
	}

    static public void CPU_HLT( /*Bitu*/int oldeip) {
        CPU_Regs.reg_eip=oldeip;
        CPU_IODelayRemoved += CPU_Cycles;
        CPU_Cycles=0;
        cpu.hlt.cs=CPU_Regs.reg_csVal.dword;
        cpu.hlt.eip=CPU_Regs.reg_eip;
    }

    static public void CPU_ENTER(boolean use32, /*Bitu*/int bytes, /*Bitu*/int level) {
        level&=0x1f;
         /*Bitu*/int sp_index=CPU_Regs.reg_esp.dword & cpu.stack.mask;
         /*Bitu*/int bp_index=CPU_Regs.reg_ebp.dword & cpu.stack.mask;
        if (!use32) {
            sp_index-=2;
            Memory.mem_writew(CPU_Regs.reg_ssPhys+sp_index,CPU_Regs.reg_ebp.word());
            CPU_Regs.reg_ebp.word(CPU_Regs.reg_esp.dword-2);
            if (level!=0) {
                for ( /*Bitu*/int i=1;i<level;i++) {
                    sp_index-=2;bp_index-=2;
                    Memory.mem_writew(CPU_Regs.reg_ssPhys+sp_index,Memory.mem_readw(CPU_Regs.reg_ssPhys+bp_index));
                }
                sp_index-=2;
                Memory.mem_writew(CPU_Regs.reg_ssPhys+sp_index,CPU_Regs.reg_ebp.word());
            }
        } else {
            sp_index-=4;
            Memory.mem_writed(CPU_Regs.reg_ssPhys+sp_index,CPU_Regs.reg_ebp.dword);
            CPU_Regs.reg_ebp.dword=CPU_Regs.reg_esp.dword-4;
            if (level!=0) {
                for ( /*Bitu*/int i=1;i<level;i++) {
                    sp_index-=4;bp_index-=4;
                    Memory.mem_writed(CPU_Regs.reg_ssPhys+sp_index,Memory.mem_readd(CPU_Regs.reg_ssPhys + bp_index));
                }
                sp_index-=4;
                Memory.mem_writed(CPU_Regs.reg_ssPhys+sp_index,CPU_Regs.reg_ebp.dword);
            }
        }
        sp_index-=bytes;
        CPU_Regs.reg_esp.dword=(CPU_Regs.reg_esp.dword & cpu.stack.notmask)|(sp_index & cpu.stack.mask);
    }

    void CPU_Reset_AutoAdjust() {
        CPU_IODelayRemoved = 0;
        Dosbox.ticksDone = 0;
        Dosbox.ticksScheduled = 0;
    }

    private static Hashtable<Integer, Long> msrs = new Hashtable<Integer, Long>();

    private static boolean inited = false;
    static public void initialize() {
        inited = false;
        cpu = new CPUBlock();
    }
    public CPU() {
        if(inited) {
            Change_Config();
            return;
        }
//		Section_prop * section=static_cast<Section_prop *>(configuration);
        inited=true;
        CPU_Regs.reg_eax.dword=0;
        CPU_Regs.reg_ebx.dword=0;
        CPU_Regs.reg_ecx.dword=0;
        CPU_Regs.reg_edx.dword=0;
        CPU_Regs.reg_edi.dword=0;
        CPU_Regs.reg_esi.dword=0;
        CPU_Regs.reg_ebp.dword=0;
        CPU_Regs.reg_esp.dword=0;

        CPU_Regs.SegSet16CS(0);
        CPU_Regs.SegSet16DS(0);
        CPU_Regs.SegSet16ES(0);
        CPU_Regs.SegSet16FS(0);
        CPU_Regs.SegSet16GS(0);
        CPU_Regs.SegSet16SS(0);

        CPU_Regs.CPU_SetFlags(CPU_Regs.IF,CPU_Regs.FMASK_ALL);		//Enable interrupts
        cpu.cr0=0xffffffff;
        //CPU_SET_CRX(0,0);						//Initialize
        cpu.stack.mask=0xffff;
        cpu.stack.notmask=0xffff0000;
        cpu.stack.big=false;
        cpu.trap_skip=false;
        //cpu.idt.SetBase(0);
        //cpu.idt.SetLimit(1023);

        /* Init the cpu cores */
        Change_Config();
        CPU_JMP(false,0,0,0);					//Setup the first cpu core
    }
    public boolean Change_Config(){
        CPU_AutoDetermineMode=CPU_AUTODETERMINE_NONE;
        //CPU_CycleLeft=0;//needed ?
        CPU_Cycles=0;

        //Prop_multival p = section.Get_multival("cycles");
        final String type = "max";//p.GetSection().Get_string("type");
        String str;
        //CommandLine cmd = new CommandLine(null, p.GetSection().Get_string("parameters"));

            if (type.equals("auto")) {
                CPU_AutoDetermineMode|=CPU_AUTODETERMINE_CYCLES;
                CPU_CycleMax=3000;
                CPU_OldCycleMax=3000;
                CPU_CyclePercUsed=100;

            } else if(type.equals("fixed")) {
                //str = cmd.FindCommand(1);
                //try {CPU_CycleMax=Integer.parseInt(str);} catch (Exception e){}
            } else {
                int rmdval=0;
                try {
                    rmdval = Integer.parseInt(type);
                    if(rmdval!=0) CPU_CycleMax=rmdval;
                } catch (Exception e) {
                }
            }

        CPU_CycleUp=10;//section.Get_int("cycleup");
        CPU_CycleDown=20;//section.Get_int("cycledown");

        if(CPU_CycleMax <= 0) CPU_CycleMax = 3000;
        if(CPU_CycleUp <= 0)   CPU_CycleUp = 500;
        if(CPU_CycleDown <= 0) CPU_CycleDown = 20;
        return true;
    }

    static private CPU test;

    public static void CPU_Init() {
        test = new CPU();
    }
}

